﻿using System;
using System.Globalization;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization;
using System.Linq.Expressions;
using System.Web;
using System.Web.Mvc;
using System.ComponentModel;
using System.Web.Routing;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

namespace Vit.Common.Test
{
    /// <summary>
    /// 断言
    /// </summary>
    public static class AssertEx
    {
        public static void VerifyCallsTo<T>(RouteCollection routes, string url, string area, string controller, string action, string id, Expression<Action<T>> operation) where T : IController
        {
            // Arrange
            var context = new Mock<HttpContextBase> { DefaultValue = DefaultValue.Mock };
            context
                .Setup(c => c.Request.AppRelativeCurrentExecutionFilePath)
                .Returns(url);

            // Act
            var routeData = routes.GetRouteData(context.Object);

            // Assert
            Assert.AreEqual(area, routeData.DataTokens["area"], "Default area is main");
            Assert.AreEqual(controller, routeData.Values["controller"], "Default controller is HomeController");
            Assert.AreEqual(action, routeData.Values["action"], "Default action is Index");
            Assert.AreEqual(id, routeData.Values["id"], "Default Id is empty string");

            VerifyCallsTo<T>(routeData, operation);
        }

        public static void VerifyCallsTo<T>(RouteData route, Expression<Action<T>> action)
                    where T : IController
        {
            Assert.IsNotNull(route);

            Assert.IsTrue(route.Values.ContainsKey("controller"), "Controller's name doesn't exists.");
            Assert.AreEqual(typeof(T).Name, route.Values["controller"].ToString() + "Controller", "Controller's name.");

            var methodCall = action.Body as MethodCallExpression;
            if (methodCall == null)
                throw new NotSupportedException("Action body must be a MethodCallExpression.");

            Assert.IsTrue(route.Values.ContainsKey("action"), "Action's name doesn't exists.");
            Assert.AreEqual(methodCall.Method.Name, route.Values["action"], "Action's name.");

            foreach (var parameter in methodCall.Method.GetParameters())
            {
                Assert.IsTrue(route.Values.ContainsKey(parameter.Name), String.Format("Argument for '{0}' doesn't exists.", parameter.Name));

                object expectedValue;
                var argumentExpr = methodCall.Arguments[parameter.Position] as ConstantExpression;
                if (argumentExpr != null)
                    expectedValue = argumentExpr.Value;
                else
                    throw new NotSupportedException("Arguments must be ConstantExpression.");
                object actualValue = TypeDescriptor.GetConverter(expectedValue.GetType()).ConvertFromString(route.Values[parameter.Name].ToString());
                Assert.AreEqual(expectedValue, actualValue, String.Format("Argument for '{0}'.", parameter.Name));
            }
        }

        /// <summary>
        /// Checks to make sure that the input delegate throws a exception of type exceptionType.
        /// </summary>
        /// <typeparam name="TExceptionType">The type of exception expected.</typeparam>
        /// <param name="blockToExecute">The block of code to execute to generate the exception.</param>
        public static void ThrowsException<TExceptionType>(Action blockToExecute) where TExceptionType : System.Exception
        {
            try
            {
                blockToExecute();
            }
            catch (Exception ex)
            {
                if (ex is TExceptionType)
                    return;
                Assert.Fail("Expected exception of type " + typeof(TExceptionType) + " but type of " + ex.GetType() + " was thrown instead.");
            }

            Assert.Fail("Expected exception of type " + typeof(TExceptionType) + " but no exception was thrown.");
        }
    }
}